// ======================================================================
// \title  CmdSequencerImpl.hpp
// \author Bocchino/Canham
// \brief  hpp file for CmdSequencer component implementation class
//
// Copyright (C) 2009-2018 California Institute of Technology.
// ALL RIGHTS RESERVED.  United States Government Sponsorship
// acknowledged.
// ======================================================================

#ifndef Svc_CmdSequencerImpl_HPP
#define Svc_CmdSequencerImpl_HPP

#include "Fw/Com/ComBuffer.hpp"
#include "Fw/Types/MemAllocator.hpp"
#include "Os/File.hpp"
#include "Os/ValidateFile.hpp"
#include "Svc/CmdSequencer/CmdSequencerComponentAc.hpp"

namespace Svc {

// Forward declaration for UTs
namespace ImmediateBase {
class CmdSequencerTester;
}
namespace Immediate {
class CmdSequencerTester;
}
namespace ImmediateEOS {
class CmdSequencerTester;
}
namespace Mixed {
class CmdSequencerTester;
}
namespace MixedRelativeBase {
class CmdSequencerTester;
}
namespace Relative {
class CmdSequencerTester;
}
namespace JoinWait {
class CmdSequencerTester;
}

class CmdSequencerComponentImpl final : public CmdSequencerComponentBase {
    friend class CmdSequencerTester;
    friend class Svc::ImmediateBase::CmdSequencerTester;
    friend class Svc::Immediate::CmdSequencerTester;
    friend class Svc::ImmediateEOS::CmdSequencerTester;
    friend class Svc::Mixed::CmdSequencerTester;
    friend class Svc::MixedRelativeBase::CmdSequencerTester;
    friend class Svc::Relative::CmdSequencerTester;
    friend class Svc::JoinWait::CmdSequencerTester;

  private:
    // ----------------------------------------------------------------------
    // Private enumerations
    // ----------------------------------------------------------------------

    //! The run mode
    enum RunMode { STOPPED, RUNNING };

    //! The step mode
    enum StepMode { AUTO, MANUAL };

  public:
    // ----------------------------------------------------------------------
    // Public classes
    // ----------------------------------------------------------------------

    //! \class Sequence
    //! \brief A sequence with unspecified binary format
    class Sequence {
      public:
        //! \class Events
        //! \brief Sequence event reporting
        class Events {
          public:
            //! Construct an Events object
            Events(Sequence& sequence  //!< The enclosing sequence
            );

          public:
            //! File CRC failure
            void fileCRCFailure(const U32 storedCRC,   //!< The CRC stored in the file
                                const U32 computedCRC  //!< The CRC computed over the file
            );

            //! File invalid
            void fileInvalid(const CmdSequencer_FileReadStage::t stage,  //!< The file read stage
                             const I32 error                             //!< The error
            );

            //! File not found
            void fileNotFound();

            //! File read error
            void fileReadError();

            //! File size error
            void fileSizeError(const U32 size  //!< The size
            );

            //! Record invalid
            void recordInvalid(const U32 recordNumber,  //!< The record number
                               const I32 error          //!< The error
            );

            //! Record mismatch
            void recordMismatch(const U32 numRecords,  //!< The number of records in the header
                                const U32 extraBytes   //!< The number of bytes beyond last record
            );

            //! Time base mismatch
            void timeBaseMismatch(const TimeBase currTimeBase,  //!< The current time base
                                  const TimeBase seqTimeBase    //!< The sequence file time base
            );

            //! Time context mismatch
            void timeContextMismatch(const FwTimeContextStoreType currTimeContext,  //!< The current time context
                                     const FwTimeContextStoreType seqTimeContext    //!< The sequence file time context
            );

            // No Records
            void noRecords();

          private:
            //! The enclosing component
            Sequence& m_sequence;
        };

      public:
        //! Construct a Sequence object
        Sequence(CmdSequencerComponentImpl& component  //!< The enclosing component
        );

        //! Destroy a Sequence object
        virtual ~Sequence();

      public:
        //! \class Header
        //! \brief A sequence header
        class Header {
          public:
            enum Constants {
                //! Serialized size of header
                SERIALIZED_SIZE =
                    sizeof(U32) + sizeof(U32) + sizeof(FwTimeBaseStoreType) + sizeof(FwTimeContextStoreType)
            };

          public:
            //! Construct a Header object
            Header();

          public:
            //! Validate the time field of the sequence header
            //! \return Success or failure
            bool validateTime(CmdSequencerComponentImpl& component  //!< Component for time and events
            );

          public:
            //! The file size
            U32 m_fileSize;

            //! The number of records in the sequence
            U32 m_numRecords;

            //! The time base of the sequence
            TimeBase m_timeBase;

            //! The context of the sequence
            FwTimeContextStoreType m_timeContext;
        };

      public:
        //! \class Record
        //! \brief A sequence record
        class Record {
          public:
            enum Descriptor {
                ABSOLUTE,        //!< Absolute time
                RELATIVE,        //!< Relative time
                END_OF_SEQUENCE  //!< end of sequence
            };

          public:
            //! Construct a Record object
            Record() : m_descriptor(END_OF_SEQUENCE) {}

          public:
            //! The descriptor
            Descriptor m_descriptor;

            //! The time tag. NOTE: timeBase and context not filled in
            Fw::Time m_timeTag;

            //! The command
            Fw::ComBuffer m_command;
        };

      public:
        //! Give the sequence representation a memory buffer
        void allocateBuffer(FwEnumStoreType identifier,   //!< The identifier
                            Fw::MemAllocator& allocator,  //!< The allocator
                            FwSizeType bytes              //!< The number of bytes
        );

        //! Deallocate the buffer
        void deallocateBuffer(Fw::MemAllocator& allocator  //!< The allocator
        );

        //! Set the file name. Also sets the log file name.
        void setFileName(const Fw::ConstStringBase& fileName);

        //! Get the file name
        //! \return The file name
        Fw::CmdStringArg& getFileName();

        //! Get the log file name
        //! \return The log file name
        Fw::LogStringArg& getLogFileName();

        //! Get the normal string file name
        //! \return The normal string file name
        Fw::String& getStringFileName();

        //! Get the sequence header
        const Header& getHeader() const;

        //! Load a sequence file
        //! \return Success or failure
        virtual bool loadFile(const Fw::ConstStringBase& fileName  //!< The file name
                              ) = 0;

        //! Query whether the sequence has any more records
        //! \return Yes or no
        virtual bool hasMoreRecords() const = 0;

        //! Get the next record in the sequence
        //! Asserts on failure
        virtual void nextRecord(Record& record  //!< The returned record
                                ) = 0;

        //! Reset the sequence to the beginning.
        //! After calling this, hasMoreRecords should return true,
        //! unless the sequence has no records
        virtual void reset() = 0;

        //! Clear the sequence records.
        //! After calling this, hasMoreRecords should return false
        virtual void clear() = 0;

      protected:
        //! The enclosing component
        CmdSequencerComponentImpl& m_component;

        //! Event reporting
        Events m_events;

        //! The sequence file name
        Fw::CmdStringArg m_fileName;

        //! Copy of file name for events
        Fw::LogStringArg m_logFileName;

        //! Copy of file name for ports
        Fw::String m_stringFileName;

        //! Serialize buffer to hold the binary sequence data
        Fw::ExternalSerializeBuffer m_buffer;

        //! The allocator ID
        FwEnumStoreType m_allocatorId;

        //! The sequence header
        Header m_header;
    };

    //! \class FPrimeSequence
    //! \brief A sequence that uses the F Prime binary format
    class FPrimeSequence : public Sequence {
      private:
        enum Constants { INITIAL_COMPUTED_VALUE = 0xFFFFFFFFU };

      public:
        //! \class CRC
        //! \brief Container for computed and stored CRC values
        struct CRC {
            //! Construct a CRC
            CRC();

            //! Initialize computed CRC
            void init();

            //! Update computed CRC
            void update(const BYTE* buffer,    //!< The buffer
                        FwSizeType bufferSize  //!< The buffer size
            );

            //! Finalize computed CRC
            void finalize();

            //! Computed CRC
            U32 m_computed;

            //! Stored CRC
            U32 m_stored;
        };

      public:
        //! Construct an FPrimeSequence
        FPrimeSequence(CmdSequencerComponentImpl& component  //!< The enclosing component
        );

      public:
        //! Load a sequence file
        //! \return Success or failure
        bool loadFile(const Fw::ConstStringBase& fileName  //!< The file name
        );

        //! Query whether the sequence has any more records
        //! \return Yes or no
        bool hasMoreRecords() const;

        //! Get the next record in the sequence.
        //! Asserts on failure
        void nextRecord(Record& record  //!< The returned record
        );

        //! Reset the sequence to the beginning.
        //! After calling this, hasMoreRecords should return true, unless
        //! the sequence has no records.
        void reset();

        //! Clear the sequence records.
        //! After calling this, hasMoreRecords should return false.
        void clear();

      private:
        //! Read a sequence file
        //! \return Success or failure
        bool readFile();

        //! Read an open sequence file
        //! \return Success or failure
        bool readOpenFile();

        //! Read a binary sequence header from the sequence file
        //! into the buffer
        //! \return Success or failure
        bool readHeader();

        //! Deserialize the binary sequence header from the buffer
        //! \return Success or failure
        bool deserializeHeader();

        //! Read records and CRC into buffer
        //! \return Success or failure
        bool readRecordsAndCRC();

        //! Extract CRC from record data
        //! \return Success or failure
        bool extractCRC();

        //! Validate the CRC
        //! \return Success or failure
        bool validateCRC();

        //! Deserialize a record from a buffer
        //! \return Serialize status
        Fw::SerializeStatus deserializeRecord(Record& record  //!< The record
        );

        //! Deserialize a record descriptor
        //! \return Serialize status
        Fw::SerializeStatus deserializeDescriptor(Record::Descriptor& descriptor  //!< The descriptor
        );

        //! Deserialize a time tag
        //! \return Serialize status
        Fw::SerializeStatus deserializeTimeTag(Fw::Time& timeTag  //!< The time tag
        );

        //! Deserialize the record size
        //! \return Serialize status
        Fw::SerializeStatus deserializeRecordSize(U32& recordSize  //!< The record size
        );

        //! Copy the serialized command into a com buffer
        //! \return Serialize status
        Fw::SerializeStatus copyCommand(Fw::ComBuffer& comBuffer,  //!< The com buffer
                                        const U32 recordSize       //!< The record size
        );

        //! Validate the sequence records in the buffer
        //! \return Success or failure
        bool validateRecords();

      private:
        //! The CRC values
        CRC m_crc;

        //! The sequence file
        Os::File m_sequenceFile;
    };

  private:
    // ----------------------------------------------------------------------
    // Private classes
    // ----------------------------------------------------------------------

    //! \class Timer
    //! \brief A class representing a timer
    class Timer {
        friend class CmdSequencerTester;
        friend class Svc::ImmediateBase::CmdSequencerTester;
        friend class Svc::Immediate::CmdSequencerTester;
        friend class Svc::ImmediateEOS::CmdSequencerTester;
        friend class Svc::Mixed::CmdSequencerTester;
        friend class Svc::MixedRelativeBase::CmdSequencerTester;
        friend class Svc::Relative::CmdSequencerTester;
        friend class Svc::JoinWait::CmdSequencerTester;

      private:
        //! The timer state
        typedef enum { SET, CLEAR } State;

      public:
        //! Construct a Timer object
        Timer() : m_state(CLEAR) {}

        //! Set the expiration time
        void set(Fw::Time time  //!< The time
        ) {
            this->m_state = SET;
            this->expirationTime = time;
        }

        //! Clear the timer
        void clear() { this->m_state = CLEAR; }

        //! Determine whether the timer is expired at a given time
        //! \return Yes or no
        bool isExpiredAt(Fw::Time time  //!< The time
        ) {
            if (this->m_state == CLEAR) {
                return false;
            } else if (Fw::Time::compare(this->expirationTime, time) == Fw::Time::GT) {
                return false;
            }
            return true;
        }

      private:
        //! The timer state
        State m_state;

        //! The expiration time
        Fw::Time expirationTime;
    };

  public:
    // ----------------------------------------------------------------------
    // Construction, initialization, and destruction
    // ----------------------------------------------------------------------

    //! Construct a CmdSequencer
    CmdSequencerComponentImpl(const char* compName  //!< The component name
    );

    //! (Optional) Set a timeout.
    //! Sequence will quit if a command takes longer than the number of
    //! seconds in the timeout value.
    void setTimeout(const U32 seconds  //!< The number of seconds
    );

    //! (Optional) Set the sequence format.
    //! CmdSequencer will use the sequence object you pass in
    //! to load and run sequences. By default, it uses an FPrimeSequence
    //! object.
    void setSequenceFormat(Sequence& sequence  //!< The sequence object
    );

    //! Give the sequence a memory buffer.
    //! Call this after constructor and init, and after setting
    //! the sequence format, but before task is spawned.
    void allocateBuffer(const FwEnumStoreType identifier,  //!< The identifier
                        Fw::MemAllocator& allocator,       //!< The allocator
                        const FwSizeType bytes             //!< The number of bytes
    );

    //! (Optional) Load a sequence to run later.
    //! When you call this function, the event ports must be connected.
    void loadSequence(const Fw::ConstStringBase& fileName  //!< The file name
    );

    //! Return allocated buffer. Call during shutdown.
    void deallocateBuffer(Fw::MemAllocator& allocator  //!< The allocator
    );

    //! Destroy a CmdDispatcherComponentBase
    ~CmdSequencerComponentImpl();

  private:
    // ----------------------------------------------------------------------
    // Handler implementations for input ports
    // ----------------------------------------------------------------------

    //! Handler for input port cmdResponseIn
    void cmdResponseIn_handler(FwIndexType portNum,             //!< The port number
                               FwOpcodeType opcode,             //!< The command opcode
                               U32 cmdSeq,                      //!< The command sequence number
                               const Fw::CmdResponse& response  //!< The command response
    );

    //! Handler for input port schedIn
    void schedIn_handler(FwIndexType portNum,  //!< The port number
                         U32 order             //!< The call order
    );

    //! Handler for input port seqRunIn
    void seqRunIn_handler(FwIndexType portNum,            //!< The port number
                          const Fw::StringBase& filename  //!< The sequence file
    );

    //! Handler for ping port
    void pingIn_handler(FwIndexType portNum,  //!< The port number
                        U32 key               //!< Value to return to pinger
    );

    //! Handler implementation for seqCancelIn
    //!
    void seqCancelIn_handler(const FwIndexType portNum /*!< The port number*/
    );

  private:
    // ----------------------------------------------------------------------
    // Command handler implementations
    // ----------------------------------------------------------------------

    //! Handler for command CS_AUTO
    //! Set the run mode to AUTO.
    void CS_AUTO_cmdHandler(FwOpcodeType opcode,  //!< The opcode
                            U32 cmdSeq            //!< The command sequence number
    );

    //! Handler for command CS_CANCEL
    //! Validate a command sequence file
    void CS_CANCEL_cmdHandler(FwOpcodeType opCode,  //!< The opcode
                              U32 cmdSeq            //!< The command sequence number
    );

    //! Handler for command CS_MANUAL
    //! Set the run mode to MANUAL.
    void CS_MANUAL_cmdHandler(FwOpcodeType opcode,  //!< The opcode
                              U32 cmdSeq            //!< The command sequence number
    );

    //! Handler for command CS_RUN
    void CS_RUN_cmdHandler(FwOpcodeType opCode,               //!< The opcode
                           U32 cmdSeq,                        //!< The command sequence number
                           const Fw::CmdStringArg& fileName,  //!< The file name
                           Svc::CmdSequencer_BlockState block /*!< Return command status when complete or not*/
    );

    //! Handler for command CS_START
    //! Start running a command sequence
    void CS_START_cmdHandler(FwOpcodeType opcode,  //!< The opcode
                             U32 cmdSeq            //!< The command sequence number
    );

    //! Handler for command CS_STEP
    //! Perform one step in a command sequence.
    //! Valid only if SequenceRunner is in MANUAL run mode.
    void CS_STEP_cmdHandler(FwOpcodeType opcode,  //!< The opcode
                            U32 cmdSeq            //!< The command sequence number
    );

    //! Handler for command CS_VALIDATE
    //! Run a command sequence file
    void CS_VALIDATE_cmdHandler(FwOpcodeType opCode,              //!< The opcode
                                U32 cmdSeq,                       //!< The command sequence number
                                const Fw::CmdStringArg& fileName  //!< The name of the sequence file
    );

    //! Implementation for CS_JOIN command handler
    //! Wait for sequences that are running to finish.
    //! Allow user to run multiple seq files in SEQ_NO_BLOCK mode
    //! then wait for them to finish before allowing more seq run request.
    void CS_JOIN_WAIT_cmdHandler(const FwOpcodeType opCode, /*!< The opcode*/
                                 const U32 cmdSeq           /*!< The command sequence number*/
    );

  private:
    // ----------------------------------------------------------------------
    // Private helper methods
    // ----------------------------------------------------------------------

    //! Load a sequence file
    //! \return Success or failure
    bool loadFile(const Fw::ConstStringBase& fileName  //!< The file name
    );

    //! Perform a Cancel command
    void performCmd_Cancel();

    //! Perform a Step command
    void performCmd_Step();

    //! Perform a Step command with a relative time
    void performCmd_Step_RELATIVE(Fw::Time& currentTime  //!< The time
    );

    //! Perform a Step command with an absolute time
    void performCmd_Step_ABSOLUTE(Fw::Time& currentTime  //!< The time
    );

    //! Record a completed command
    void commandComplete(const FwOpcodeType opCode  //!< The opcode
    );

    //! Record a sequence complete event
    void sequenceComplete();

    //! Record an error
    void error();

    //! Record an error in executing a sequence command
    void commandError(const U32 number,           //!< The command number
                      const FwOpcodeType opCode,  //!< The command opcode
                      const U32 error             //!< The error code
    );

    //! Require a run mode
    //! \return Whether we are in the correct mode
    bool requireRunMode(RunMode mode  //!< The required mode
    );

    //! Set command timeout timer
    void setCmdTimeout(const Fw::Time& currentTime  //!< The current time
    );

  private:
    // ----------------------------------------------------------------------
    // Private member variables
    // ----------------------------------------------------------------------

    //! The F Prime sequence
    FPrimeSequence m_FPrimeSequence;

    //! The abstract sequence
    Sequence* m_sequence;

    //! The number of Load commands executed
    U32 m_loadCmdCount;

    //! The number of Cancel commands executed
    U32 m_cancelCmdCount;

    //! The number of errors
    U32 m_errorCount;

    //! The run mode
    RunMode m_runMode;

    //! The step mode
    StepMode m_stepMode;

    //! The sequence record currently being processed
    Sequence::Record m_record;

    //! The command time timer
    Timer m_cmdTimer;

    //! The number of commands executed in this sequence
    U32 m_executedCount;

    //! The total number of commands executed across all sequences
    U32 m_totalExecutedCount;

    //! The total number of sequences completed
    U32 m_sequencesCompletedCount;

    //! timeout value
    U32 m_timeout;

    //! timeout timer
    Timer m_cmdTimeoutTimer;

    //! Block mode for command status
    Svc::CmdSequencer_BlockState::t m_blockState;
    FwOpcodeType m_opCode;
    U32 m_cmdSeq;
    bool m_join_waiting;
};

}  // namespace Svc

#endif
